package com.glowdayz.common;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspWriter;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.log4j.Logger;

public class MultipartBox {
	
	// Logger 클래스의 인스턴스를 받아온다.
	static Logger logger = Logger.getLogger(MultipartBox.class);
	
	// SingleTone 선언
	private static MultipartBox current = null;

	/** 임시 폴더 경로 */
	private String tempDir = "/home/glowmee/upload/temp";

	/** 저장폴더 경로 */
	int yy = Calendar.getInstance().get(Calendar.YEAR);
	int mm = Calendar.getInstance().get(Calendar.MONTH) + 1;
	int dd = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
	private String uploadDir = String.format("/home/glowmee/upload/%04d%02d%02d", yy, mm, dd);

	/** 메모리를 사용할 최대 용량 */
	private int useMemorySize = 1 * 1024 * 1024;

	/** 업로드를 허용할 최대 용량 */
	private long maxUploadSize = 20 * 1024 * 1024;

	/** 기본 인코딩 형식 */
	private String encType = "UTF-8";

	/** 업로드를 허용할 확장자 */
	//private String[] useExtName = null;

	/** 문자열 파라미터를 저장할 객체 */
	Map<String, String[]> params = null;

	/** 업로드된 파일의 리스트 */
	ArrayList<FileInfo> fileList = null;

	/******* 파일 정보의 구조를 정의하는 InnerClass *******/
	public static class FileInfo {
		
		// Logger 클래스의 인스턴스를 받아온다.
		static Logger logger = Logger.getLogger(FileInfo.class);
		
		private String fieldName;
		private int idAttacheFile;
		private String tableName;
		private String tableKey;
		private String fileOrgName;
		private String fileSaveName;
		private String fileDir;
		private long fileSize;
		private String fileType;
		private int sortKey;
		private int isDisplay;
		private int isUser;
		private String create_date;
		private String modified_date;

		public FileInfo(String fieldName, String fileOrgName,
				String fileSaveName, String fileDir, long fileSize,
				String fileType) {
			super();
			this.fieldName = fieldName;
			this.fileOrgName = fileOrgName;
			this.fileSaveName = fileSaveName;
			this.fileDir = fileDir;
			this.fileSize = fileSize;
			this.fileType = fileType;
		}
		
		public FileInfo(String fieldName, int idAttacheFile, String fileOrgName,
				String fileSaveName, String fileDir, long fileSize,
				String fileType) {
			super();
			this.fieldName = fieldName;
			this.idAttacheFile = idAttacheFile;
			this.fileOrgName = fileOrgName;
			this.fileSaveName = fileSaveName;
			this.fileDir = fileDir;
			this.fileSize = fileSize;
			this.fileType = fileType;
		}
		
		public FileInfo(String fieldName, int idAttacheFile, String fileOrgName,
				String fileSaveName, String fileDir, long fileSize,
				String fileType, int sortKey) {
			super();
			this.fieldName = fieldName;
			this.idAttacheFile = idAttacheFile;
			this.fileOrgName = fileOrgName;
			this.fileSaveName = fileSaveName;
			this.fileDir = fileDir;
			this.fileSize = fileSize;
			this.fileType = fileType;
			this.sortKey = sortKey;
		}

		public FileInfo(String fieldName, int idAttacheFile, String tableName,
				String tableKey, String fileOrgName, String fileSaveName,
				String fileDir, long fileSize, String fileType, int sortKey,
				int isDisplay, int isUser, String create_date,
				String modified_date) {
			super();
			this.fieldName = fieldName;
			this.idAttacheFile = idAttacheFile;
			this.tableName = tableName;
			this.tableKey = tableKey;
			this.fileOrgName = fileOrgName;
			this.fileSaveName = fileSaveName;
			this.fileDir = fileDir;
			this.fileSize = fileSize;
			this.fileType = fileType;
			this.sortKey = sortKey;
			this.isDisplay = isDisplay;
			this.isUser = isUser;
			this.create_date = create_date;
			this.modified_date = modified_date;
		}

		public String getFieldName() {
			return fieldName;
		}

		public int getIdAttacheFile() {
			return idAttacheFile;
		}

		public String getTableName() {
			return tableName;
		}
		
		public String getTableKey() {
			return tableKey;
		}

		public String getFileOrgName() {
			return fileOrgName;
		}

		public String getFileSaveName() {
			return fileSaveName;
		}

		public String getFileDir() {
			return fileDir;
		}

		public long getFileSize() {
			return fileSize;
		}

		public String getFileType() {
			return fileType;
		}

		public int getSortKey() {
			return sortKey;
		}

		public int getIsDisplay() {
			return isDisplay;
		}

		public int getIsUser() {
			return isUser;
		}

		public String getCreate_date() {
			return create_date;
		}

		public String getModified_date() {
			return modified_date;
		}

		public String toString() {
			String params = null;
			
			try {
				params = "fieldName=" + URLEncoder.encode(fieldName, "UTF-8")
						+ "&fileOrgName=" + URLEncoder.encode(fileOrgName, "UTF-8")
						+ "&fileSaveName=" + URLEncoder.encode(fileSaveName, "UTF-8")
						+ "&fileDir=" + URLEncoder.encode(fileDir, "UTF-8")
						+ "&fileSize=" + fileSize
						+ "&fileType=" + URLEncoder.encode(fileType, "UTF-8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			
			return params;
		}

		public String toString(String encType) {
			String params = null;
			
			try {
				params = "fieldName=" + URLEncoder.encode(fieldName, encType)
						+ "&fileOrgName=" + URLEncoder.encode(fileOrgName, encType)
						+ "&fileSaveName=" + URLEncoder.encode(fileSaveName, encType)
						+ "&fileDir=" + URLEncoder.encode(fileDir, encType)
						+ "&fileSize=" + fileSize
						+ "&fileType=" + URLEncoder.encode(fileType, encType);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			
			return params;
		}
	}
	
	/** SingleTone 객체 생성 메서드 */
	public static MultipartBox getInstance() {
		if (current == null) {
			current = new MultipartBox();
		}
		
		return current;
	}
	
	/** 생성자가 재정의된 경우에는 getInstance도 재정의 >> 생성자의 로직은 그대로 복사 */
	public static MultipartBox getInstance(String tempDir, String uploadDir, int useMemorySize,
			long maxUploadSize, String encType) {
		if (current == null) {
			current = new MultipartBox();
		}
		
		current.tempDir = tempDir;
		current.uploadDir = uploadDir;
		current.useMemorySize = useMemorySize;
		current.maxUploadSize = maxUploadSize;
		current.encType = encType;
		
		return current;
	}

	private MultipartBox() {
		super();
	}

	private void initRepo() {
		params = new HashMap<String, String[]>();
		fileList = new ArrayList<FileInfo>();
	}

	public String getTempDir() {
		return tempDir;
	}

	public void setTempDir(String tempDir) {
		this.tempDir = tempDir;
	}

	public String getUploadDir() {
		return uploadDir;
	}

	public void setUploadDir(String uploadDir) {
		this.uploadDir = uploadDir;
	}

	public int getUseMemorySize() {
		return useMemorySize;
	}

	public void setUseMemorySize(int useMemorySize) {
		this.useMemorySize = useMemorySize;
	}

	public long getMaxUploadSize() {
		return maxUploadSize;
	}

	public void setMaxUploadSize(long maxUploadSize) {
		this.maxUploadSize = maxUploadSize;
	}

	public String getEncType() {
		return encType;
	}

	public void setEncType(String encType) {
		this.encType = encType;
	}

	//public String[] getUseExtName() {
	//	return useExtName;
	//}

	//public void setUseExtName(String[] useExtName) {
	//	this.useExtName = useExtName;
	//}

	/**
	 * 업로드를 수행한다. 파일의 정보는 ArrayList<FileInfo>에 담고,
	 * 텍스트 정보는 params에 담는다. 최종적으로 업로드 된 파일의 리스트를
	 * 리턴한다.
	 * @param request
	 * @return ArrayList<FileInfo>
	 */
	public ArrayList<FileInfo> upload(HttpServletRequest request) {
		logger.debug("=============== method start >> upload");
		
		// multipart로 전송되었는지 여부
		boolean isMultipart = ServletFileUpload.isMultipartContent(request);
		
		logger.debug("isMultipart=" + isMultipart);

		if (isMultipart) {
			/****** 업로드를 받기 위한 환경설정 시작 ******/
			// 파일이 업로드 될 임시 폴더
			File tmpDir = new File(this.tempDir);
			if (!tmpDir.exists()) {
				tmpDir.mkdirs();
			}
			
			// 파일이 보관될 폴더의 존재 여부 검사
			File rDir = new File(this.uploadDir);
			if (!rDir.exists()) {
				rDir.mkdirs();
			}
			
			// 1메가가 넘지 않으면 메모리에서 바로 복사.
			// 그렇지 않으면 임시 폴더 사용
			DiskFileItemFactory factory = new DiskFileItemFactory();
			factory.setSizeThreshold(this.useMemorySize);
			factory.setRepository(tmpDir);
			
			/****** 업로드 시작 ******/
			ServletFileUpload upload = new ServletFileUpload(factory);
			// 최대 파일 크기
			upload.setSizeMax(this.maxUploadSize);
			// 실제 업로드를 수행하여 파일들의 목록을 얻기
			try {
				List<FileItem> items = upload.parseRequest(request);
				
				// 업로드 정보를 저장할 저장소 활성화
				initRepo();
				
				/********* 업로드 된 파일의 정보 처리 *************/
				for (int i=0; i<items.size(); i++) {
					FileItem f = items.get(i);
					
					// 파일이외의 일반 데이터인지 판별
					if (f.isFormField())  {
						// Map에 데이터 추가
						try {
							String paramName = f.getFieldName();
							String paramValue = f.getString(this.encType);
							
							/** 값을 배열로 생성 */
							// 1) 기존에 같은 이름으로 배열이 저장되어 있다면 꺼낸다.
							String[] values = params.get(paramName);
							
							if (values == null) {
								// 1-1) 없다면, 새로운 배열을 생성
								values = new String[] {paramValue};
							} else {
								// 1-2) 있다면, 기존의 배열보다 1칸 큰 새로운 배열을 생성
								String[] tempValues = new String[values.length+1];
								// 1-3) 기존 배열의 값을 새로운 배열에 모두 복사 --> 마지막 한 칸 남음
								System.arraycopy(values, 0, tempValues, 0, values.length);
								// 1-4) 마지막 한 칸에 이번에 추출한 값을 추가
								tempValues[tempValues.length-1] = paramValue;
								// 1-5) 새로 생성된 배열을 기존의 배열과 바꾼다.
								values = tempValues;
							}
							// 2) 생성된 배열을 Map에 추가
							params.put(paramName, values);
							logger.debug("[TEXT PARAMS] " + paramName + "=" + paramValue);
						} catch (UnsupportedEncodingException e) {
							logger.error(e.getLocalizedMessage());
							logger.error(e.getStackTrace());
						}
					} else {
						String fieldName = f.getFieldName();
						String fileName = f.getName();
						String contentType = f.getContentType();
						long fileSize = f.getSize();
						
						if (fileName.equals("")) {
							continue;
						}
						
						// 파일이름에서 확장자만 추출
						String ext = fileName.substring(fileName.lastIndexOf("."));
						
						// 웹 서버에 저장될 이름을 지정
						Calendar c = Calendar.getInstance();
						long ms = c.getTimeInMillis();
						File uploadFile = null;
						String upload_name = null;
						boolean isFind = false;
						
						while (!isFind) {
							long random = Util.getInstance().random(100, 999);
							upload_name = ms + random + ext;
							uploadFile = new File(this.uploadDir, upload_name);
							
							if (!uploadFile.exists()) {
								isFind = true;
							}
						}
						
						// 임시 폴더에 존재하는 파일을 보관용 폴더에 복사하고, 임시파일 삭제
						try {
							
							f.write(uploadFile);
							f.delete();
							
							// 업로드 성공후, 정보를 종합하여 ArrayList에 추가
							FileInfo info = new FileInfo(fieldName, 
									fileName, upload_name, this.uploadDir, fileSize, contentType);
							fileList.add(info);
							
						} catch (IOException ioe) {
							logger.error(ioe.getLocalizedMessage());
							logger.error(ioe.getStackTrace());
						} catch (Exception e) {
							logger.error(e.getLocalizedMessage());
							logger.error(e.getStackTrace());
						}
					}
				}
				
			} catch (FileUploadException e) {
				logger.error(e.getLocalizedMessage());
				logger.error(e.getStackTrace());
			}
		} else {
			return null;
		}
		
		logger.debug("=============== method end >> upload");

		return fileList;
	}
	
	public FileInfo upload2(HttpServletRequest request) {
		logger.debug("=============== method start >> upload");
		
		// multipart로 전송되었는지 여부
		boolean isMultipart = ServletFileUpload.isMultipartContent(request);
		
		logger.debug("isMultipart=" + isMultipart);
		
		FileInfo info = null;

		if (isMultipart) {
			/****** 업로드를 받기 위한 환경설정 시작 ******/
			// 파일이 업로드 될 임시 폴더
			File tmpDir = new File(this.tempDir);
			if (!tmpDir.exists()) {
				tmpDir.mkdirs();
			}
			
			// 파일이 보관될 폴더의 존재 여부 검사
			File rDir = new File(this.uploadDir);
			if (!rDir.exists()) {
				rDir.mkdirs();
			}
			
			// 1메가가 넘지 않으면 메모리에서 바로 복사.
			// 그렇지 않으면 임시 폴더 사용
			DiskFileItemFactory factory = new DiskFileItemFactory();
			factory.setSizeThreshold(this.useMemorySize);
			factory.setRepository(tmpDir);
			
			/****** 업로드 시작 ******/
			ServletFileUpload upload = new ServletFileUpload(factory);
			// 최대 파일 크기
			upload.setSizeMax(this.maxUploadSize);
			// 실제 업로드를 수행하여 파일들의 목록을 얻기
			try {
				List<FileItem> items = upload.parseRequest(request);
				
				// 업로드 정보를 저장할 저장소 활성화
				initRepo();
				
				/********* 업로드 된 파일의 정보 처리 *************/
				for (int i=0; i<items.size(); i++) {
					FileItem f = items.get(i);
					
					// 파일이외의 일반 데이터인지 판별
					if (f.isFormField())  {
						// Map에 데이터 추가
						try {
							String paramName = f.getFieldName();
							String paramValue = f.getString(this.encType);
							
							/** 값을 배열로 생성 */
							// 1) 기존에 같은 이름으로 배열이 저장되어 있다면 꺼낸다.
							String[] values = params.get(paramName);
							
							if (values == null) {
								// 1-1) 없다면, 새로운 배열을 생성
								values = new String[] {paramValue};
							} else {
								// 1-2) 있다면, 기존의 배열보다 1칸 큰 새로운 배열을 생성
								String[] tempValues = new String[values.length+1];
								// 1-3) 기존 배열의 값을 새로운 배열에 모두 복사 --> 마지막 한 칸 남음
								System.arraycopy(values, 0, tempValues, 0, values.length);
								// 1-4) 마지막 한 칸에 이번에 추출한 값을 추가
								tempValues[tempValues.length-1] = paramValue;
								// 1-5) 새로 생성된 배열을 기존의 배열과 바꾼다.
								values = tempValues;
							}
							// 2) 생성된 배열을 Map에 추가
							params.put(paramName, values);
							logger.debug("[TEXT PARAMS] " + paramName + "=" + paramValue);
						} catch (UnsupportedEncodingException e) {
							logger.error(e.getLocalizedMessage());
							logger.error(e.getStackTrace());
						}
					} else {
						String fieldName = f.getFieldName();
						String fileName = f.getName();
						String contentType = f.getContentType();
						long fileSize = f.getSize();
						
						if (fileName.equals("")) {
							continue;
						}
						
						// 파일이름에서 확장자만 추출
						String ext = fileName.substring(fileName.lastIndexOf("."));
						
						// 웹 서버에 저장될 이름을 지정
						Calendar c = Calendar.getInstance();
						long ms = c.getTimeInMillis();
						File uploadFile = null;
						String upload_name = null;
						boolean isFind = false;
						
						while (!isFind) {
							long random = Util.getInstance().random(100, 999);
							upload_name = ms + random + ext;
							uploadFile = new File(this.uploadDir, upload_name);
							
							if (!uploadFile.exists()) {
								isFind = true;
							}
						}
						
						// 임시 폴더에 존재하는 파일을 보관용 폴더에 복사하고, 임시파일 삭제
						try {
							
							f.write(uploadFile);
							f.delete();
							
							// 업로드 성공후, 정보를 종합하여 ArrayList에 추가
							info = new FileInfo(fieldName, 
									fileName, upload_name, this.uploadDir, fileSize, contentType);
							fileList.add(info);
							
						} catch (IOException ioe) {
							logger.error(ioe.getLocalizedMessage());
							logger.error(ioe.getStackTrace());
						} catch (Exception e) {
							logger.error(e.getLocalizedMessage());
							logger.error(e.getStackTrace());
						}
					}
				}
				
			} catch (FileUploadException e) {
				logger.error(e.getLocalizedMessage());
				logger.error(e.getStackTrace());
			}
		}
		
		logger.debug("=============== method end >> upload");

		return info;
	}
	
	public String[] getValues(String key) {
		return params.get(key);
	}
	
	/**
	 * 문자열 형식의 파라미터에 대한 값 리턴
	 */
	public String getValue(String key) {
		String[] temp = this.getValues(key);
		String value = "";
		
		if (temp == null) {
			value = "";
		} else {
			value = temp[0].trim();
		}
		return value;
	}
	
	/**
	 * 파라미터를 숫자로 받기 - 변환이 불가능한 형태일 경우 "-1"
	 */
	public int getInt(String key) {
		int value = -1;
		String param = this.getValue(key);
		
		// 파라미터가 숫자 형태인지 검사
		if (PatternChecker.getInstance().isValue(param)) {
			if (PatternChecker.getInstance().isNum(param)) {
				value = Integer.parseInt(param);
			}
		}
		
		return value;
	}
	
	/**
	 * 다운로드 처리를 위한 파일 스트림 출력
	 * --> 파일을 읽어서 출력한다.
	 */
	public boolean printStram(FileInfo info, HttpServletResponse response, JspWriter out) {		
		// 파일의 존재여부 검사
		File f = new File(info.getFileDir() + "/" + info.getFileSaveName());
		
		// 파일이 없으므로 다운로드 처리 실패
		if (!f.exists()) {
			return false;
		}

		// 파일다운로드
		try {
			// 파일 읽어들이기
			FileInputStream fin = new FileInputStream(f);
			int size = fin.available();
			
			// JSP의 response 내장객체를 통해서 OutputStream을 생성하면,
			// 웹 브라우저에게 파일을 저장하도록 지시할 수 있다. --> 다운로드
			OutputStream fos = response.getOutputStream();

			// 파일의 용량만큼 byte[]생성 --> 이 안에 파일의 내용을 저장한다.
			// --> Stream
			byte b[] = new byte[size];
			
			// 파일이름 인코딩
			String encFileName = URLEncoder.encode(info.getFileOrgName(), this.encType);

			// 파일 내용 외에 다른 내용이 전송되면, 파일이 깨지게 되므로 다른 내용에 대한 출력을 모두 비운다.
			out.clear();
			response.reset();
			
			// <%@ page ... 페이지 지시자를 JavaCode로 지정
			response.setHeader("Content-Type", info.getFileType() + "; charset=" + this.encType);
			response.setHeader("Content-Disposition","attachment; filename=" + encFileName + ";");
			response.setContentLength(size);

			// 파일 읽기
			fin.read(b);
			
			// 파일 쓰기 --> 브라우저에게...
			fos.write(b);
			
			// 스트림 닫기
			fos.close();
			fin.close();
		} catch(Exception e) { 
			logger.error(e.getLocalizedMessage());
			logger.error(e.getStackTrace());
		}

		f = null;
		
		return true;
	}
	
	public void deleteFiles(ArrayList<FileInfo> list) {
		for (int i=0; i<list.size(); i++) {
			FileInfo item = list.get(i);
			
			String path = item.getFileDir() + "/" + item.getFileSaveName();
			
			File f = new File(path);
			
			if (f.exists()) {
				f.delete();
			}
		}
	}
	
	public void deleteFile(FileInfo item) {
		String path = item.getFileDir() + "/" + item.getFileSaveName();
		
		File f = new File(path);
		
		if (f.exists()) {
			f.delete();
		}
	}
}